package org.example.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component// 通知类是作为springbean存在,交给spring创建
@Aspect// 声明为通知类
public class MyAdvice {
    // 通知类,增强的共性功能,在切入点前后执行的操作

    /*
     *
     *   关于切点表达式
     *       配置在接口上,所有实现类都会被切面影响
     *       execution(void org.example.service.UserService.update())
     *
     *       配置在实现类上,只是针对具体的这个实现类,会被切面影响
     *       execution(void org.example.service.impl.UserServiceImpl.update())
     *
     *
     *       * 代表一个组件
     *       .. 代表N个组件
     *
     *           返回值中:
     *               (1) 具体类型
     *               (2) * 表示任意返回值类型
     *               (3) 不存在 .. 返回值
     *           包结构中:
     *               (1) 具体包结构
     *               (2) * 代表一层包
     *               (3) .. 代表N层包
     *           方法名中:
     *               (1) 具体的方法名
     *               (2) 名称前缀 + * 指的是以这个制定前缀开头的任意方法名(get*: getById/getAll....)
     *           形参的写法:
     *               (1) * 代表一个参数
     *               (2) .. 代表任意个参数
     *
     *
     *       匹配org.example包下的任意包中的UserService类或接口中所有get开头的带有一个参数的方法:
     *       execution(public * org.example.*.UserService.get*(*))
     *
     *       匹配org包下的任意包中的UserService类或接口中所有名称为getById的方法
     *       execution(public User org..UserService.getById(..))
     *
     * */

    // 设置切点(功能增强了的方法)
    // UserService中的方法可能被重载,所以需要指明方法的位置以及参数和返回值
    @Pointcut("execution(void org.example.service.UserService.delete(..))")
    public void pt() {
    } // 起到变量的作用,存储上面的路径,@Pointcut只能写在方法上面

    // 创建通知方法 -----------------------------------------------------------------
    //// 前置通知,在业务执行前进行增强
    //@Before("pt()")
    //public void m1(){
    //    System.out.println("前置增强");
    //}
    //
    //// 后置通知
    //@After("pt()")
    //public void m2(){
    //    System.out.println("后置增强");
    //}
    //
    //// 返回后通知
    //@AfterReturning("pt()")
    //public void m3(){
    //    System.out.println("返回后增强");
    //}
    //

    /// / 异常后增强
    //@AfterThrowing("pt()")
    //public void m4(){
    //    System.out.println("异常后增强");
    //}

    // 获取切点数据 JoinPoint -------------------------------------------------------------
    // 前置通知,在业务执行前进行增强
    //@Before("pt()")
    //public void m1(JoinPoint jp) {
    //    System.out.print("前置增强,取得的参数为: ");
    //    Object[] args = jp.getArgs();
    //    System.out.println(Arrays.toString(args));
    //}

    //// 后置通知
    //@After("pt()")
    //public void m2(JoinPoint jp) {
    //    System.out.print("后置增强,取得的参数为: ");
    //    Object[] args = jp.getArgs();
    //    System.out.println(Arrays.toString(args));
    //}
    //
    //@Pointcut("execution(* org.example.service.UserService.getName(..))")
    //public void pt1() {}

    //// 返回后通知
    //@AfterReturning(value = "pt1()", returning = "s")// 表示用s来接收返回值,这个s要与下面m3方法的参数s一致
    //public void m3(String s) {
    //    System.out.println("返回后增强, 取得返回值: " + s);
    //}

    //// 异常后增强
    //@AfterThrowing(value = "pt()",throwing = "t")// 异常将有t变量接收
    //public void m4(Throwable t){
    //    System.out.println("异常后增强, 接收到的异常信息为: " + t.getMessage());
    //}

    // 环绕通知 ----------------------------------------------------------------------
    /*
    *
    *   环绕通知 和 同时使用前置与后置通知的区别:
    *       核心业务的调用时机由环绕通知方法来决定, 而在前置或后置通知中,无论是否增强,核心业务都会正常执行
    *
    * */
    /*@Pointcut("execution(* org.example.service.UserService.getName(..))")
    public void pt2() {}

    @Around("pt2()")
    public Object m4(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知开始");

        // 调用核心业务, 只有执行proceed方法, 核心业务才会执行
        Object result = pjp.proceed();

        Signature signature = pjp.getSignature();
        System.out.println("1: " + signature.getDeclaringTypeName());// 打印这个方法来自于哪个类
        System.out.println("2: " + signature.getName());// 打印这个方法名

        System.out.println("环绕通知结束");

        return result;
    }*/

    // 项目应用 测试接口万次执行效率
    @Around("execution(* org.example.service.UserService.get*(..))")
    public void m1(ProceedingJoinPoint pjp) throws Throwable {
        // 取得方法名
        String methodName = pjp.getSignature().getName();

        // 起始时间
        long start = System.currentTimeMillis();

        // 执行10万次业务
        for (int i = 0; i < 100000; i++) {
            // 执行核心业务
            pjp.proceed();
        }

        // 结束时间
        long end = System.currentTimeMillis();

        System.out.println(methodName + "方法经过十万次执行消耗时间: " + (end - start));
    }

}
















